Skip to contentMethod: getAttributeAxioms(Object, FieldSpecification, Descriptor)
1: /*
2: * JOPA
3: * Copyright (C) 2024 Czech Technical University in Prague
4: *
5: * This library is free software; you can redistribute it and/or
6: * modify it under the terms of the GNU Lesser General Public
7: * License as published by the Free Software Foundation; either
8: * version 3.0 of the License, or (at your option) any later version.
9: *
10: * This library is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13: * Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public
16: * License along with this library.
17: */
18: package cz.cvut.kbss.jopa.oom;
19:
20: import cz.cvut.kbss.jopa.exceptions.StorageAccessException;
21: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
22: import cz.cvut.kbss.jopa.model.metamodel.Attribute;
23: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
24: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
25: import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType;
26: import cz.cvut.kbss.jopa.model.metamodel.QueryAttribute;
27: import cz.cvut.kbss.jopa.oom.exception.EntityDeconstructionException;
28: import cz.cvut.kbss.jopa.oom.exception.EntityReconstructionException;
29: import cz.cvut.kbss.jopa.oom.exception.UnpersistedChangeException;
30: import cz.cvut.kbss.jopa.sessions.AbstractUnitOfWork;
31: import cz.cvut.kbss.jopa.sessions.UnitOfWork;
32: import cz.cvut.kbss.jopa.sessions.cache.CacheManager;
33: import cz.cvut.kbss.jopa.sessions.cache.Descriptors;
34: import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor;
35: import cz.cvut.kbss.jopa.sessions.util.LoadingParameters;
36: import cz.cvut.kbss.jopa.utils.Configuration;
37: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
38: import cz.cvut.kbss.ontodriver.Connection;
39: import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor;
40: import cz.cvut.kbss.ontodriver.descriptor.AxiomValueDescriptor;
41: import cz.cvut.kbss.ontodriver.descriptor.ListValueDescriptor;
42: import cz.cvut.kbss.ontodriver.descriptor.ReferencedListDescriptor;
43: import cz.cvut.kbss.ontodriver.descriptor.ReferencedListValueDescriptor;
44: import cz.cvut.kbss.ontodriver.descriptor.SimpleListDescriptor;
45: import cz.cvut.kbss.ontodriver.descriptor.SimpleListValueDescriptor;
46: import cz.cvut.kbss.ontodriver.exception.OntoDriverException;
47: import cz.cvut.kbss.ontodriver.model.Assertion;
48: import cz.cvut.kbss.ontodriver.model.Axiom;
49: import cz.cvut.kbss.ontodriver.model.AxiomImpl;
50: import cz.cvut.kbss.ontodriver.model.NamedResource;
51: import cz.cvut.kbss.ontodriver.model.Value;
52: import org.slf4j.Logger;
53: import org.slf4j.LoggerFactory;
54:
55: import java.net.URI;
56: import java.util.Collection;
57: import java.util.Collections;
58: import java.util.HashMap;
59: import java.util.List;
60: import java.util.Map;
61: import java.util.Objects;
62: import java.util.Set;
63:
64: import static cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException.individualAlreadyManaged;
65:
66: public class ObjectOntologyMapperImpl implements ObjectOntologyMapper, EntityMappingHelper {
67:
68: private static final Logger LOG = LoggerFactory.getLogger(ObjectOntologyMapperImpl.class);
69:
70: private final AbstractUnitOfWork uow;
71: private final Connection storageConnection;
72:
73: private final AxiomDescriptorFactory descriptorFactory;
74: private final EntityConstructor entityBuilder;
75: private final EntityDeconstructor entityBreaker;
76: private Map<URI, Object> instanceRegistry;
77: private final PendingReferenceRegistry pendingReferences;
78:
79: private final EntityInstanceLoader defaultInstanceLoader;
80: private final EntityInstanceLoader twoStepInstanceLoader;
81:
82: private final EntityReferenceFactory referenceFactory;
83:
84: public ObjectOntologyMapperImpl(AbstractUnitOfWork uow, Connection connection) {
85: this.uow = Objects.requireNonNull(uow);
86: this.storageConnection = Objects.requireNonNull(connection);
87: this.descriptorFactory = new AxiomDescriptorFactory();
88: this.instanceRegistry = new HashMap<>();
89: this.pendingReferences = new PendingReferenceRegistry();
90: this.entityBuilder = new EntityConstructor(this, uow.getLoadStateRegistry());
91: this.entityBreaker = new EntityDeconstructor(this);
92:
93: this.defaultInstanceLoader = DefaultInstanceLoader.builder().connection(storageConnection)
94: .metamodel(uow.getMetamodel())
95: .descriptorFactory(descriptorFactory)
96: .entityBuilder(entityBuilder).cache(getCache())
97: .loadStateRegistry(uow.getLoadStateRegistry()).build();
98: this.twoStepInstanceLoader = TwoStepInstanceLoader.builder().connection(storageConnection)
99: .metamodel(uow.getMetamodel())
100: .descriptorFactory(descriptorFactory)
101: .entityBuilder(entityBuilder).cache(getCache())
102: .loadStateRegistry(uow.getLoadStateRegistry()).build();
103: this.referenceFactory = new EntityReferenceFactory(uow.getMetamodel(), uow);
104: }
105:
106: private CacheManager getCache() {
107: return uow.getLiveObjectCache();
108: }
109:
110: @Override
111: public <T> boolean containsEntity(Class<T> cls, URI identifier, Descriptor descriptor) {
112: assert cls != null;
113: assert identifier != null;
114: assert descriptor != null;
115:
116: final EntityType<T> et = getEntityType(cls);
117: final NamedResource classUri = NamedResource.create(et.getIRI().toURI());
118: final Axiom<NamedResource> ax = new AxiomImpl<>(NamedResource.create(identifier),
119: Assertion.createClassAssertion(false), new Value<>(classUri));
120: try {
121: return storageConnection.contains(ax, descriptor.getContexts());
122: } catch (OntoDriverException e) {
123: throw new StorageAccessException(e);
124: }
125: }
126:
127: @Override
128: public <T> T loadEntity(LoadingParameters<T> loadingParameters) {
129: assert loadingParameters != null;
130:
131: this.instanceRegistry = new HashMap<>();
132: return loadEntityInternal(loadingParameters);
133: }
134:
135: private <T> T loadEntityInternal(LoadingParameters<T> loadingParameters) {
136: final IdentifiableEntityType<T> et = getEntityType(loadingParameters.getEntityClass());
137: final T result;
138: if (et.hasSubtypes()) {
139: result = twoStepInstanceLoader.loadEntity(loadingParameters);
140: } else {
141: result = defaultInstanceLoader.loadEntity(loadingParameters);
142: }
143: if (result != null) {
144: final LoadStateDescriptor<T> loadStateDescriptor = uow.getLoadStateRegistry().get(result);
145: assert loadStateDescriptor != null;
146: getCache().add(loadingParameters.getIdentifier(), result, new Descriptors(loadingParameters.getDescriptor(), loadStateDescriptor));
147: }
148: return result;
149: }
150:
151: @Override
152: public <T> T getReference(LoadingParameters<T> loadingParameters) {
153: assert loadingParameters != null;
154:
155: return referenceFactory.createReferenceProxy(loadingParameters);
156: }
157:
158: @Override
159: public <T> IdentifiableEntityType<T> getEntityType(Class<T> cls) {
160: return uow.getMetamodel().entity(cls);
161: }
162:
163: @Override
164: public boolean isManagedType(Class<?> cls) {
165: return uow.isEntityType(cls);
166: }
167:
168: @Override
169: public <T> void loadFieldValue(T entity, FieldSpecification<? super T, ?> fieldSpec, Descriptor descriptor) {
170: assert entity != null;
171: assert fieldSpec != null;
172: assert descriptor != null;
173:
174: LOG.trace("Lazily loading value of field {} of entity {}.", fieldSpec, uow.stringify(entity));
175:
176: final EntityType<T> et = (EntityType<T>) getEntityType(entity.getClass());
177: final URI identifier = EntityPropertiesUtils.getIdentifier(entity, et);
178:
179: if (et.hasQueryAttribute(fieldSpec.getName())) {
180: QueryAttribute<? super T, ?> queryAttribute = (QueryAttribute<? super T, ?>) fieldSpec;
181: entityBuilder.setQueryAttributeFieldValue(entity, queryAttribute, et);
182: return;
183: }
184:
185: final AxiomDescriptor axiomDescriptor =
186: descriptorFactory.createForFieldLoading(identifier, fieldSpec, descriptor, et);
187: try {
188: final Collection<Axiom<?>> axioms = storageConnection.find(axiomDescriptor);
189: entityBuilder.setFieldValue(entity, fieldSpec, axioms, et, descriptor);
190: } catch (OntoDriverException e) {
191: throw new StorageAccessException(e);
192: } catch (IllegalArgumentException e) {
193: throw new EntityReconstructionException(e);
194: }
195: }
196:
197: @Override
198: public <T> void persistEntity(URI identifier, T entity, Descriptor descriptor) {
199: assert identifier != null;
200: assert entity != null;
201: assert descriptor != null;
202:
203: @SuppressWarnings("unchecked") final EntityType<T> et = (EntityType<T>) getEntityType(entity.getClass());
204: try {
205: entityBreaker.setReferenceSavingResolver(new ReferenceSavingResolver(this));
206: final AxiomValueGatherer axiomBuilder = entityBreaker.mapEntityToAxioms(identifier, entity, et, descriptor);
207: axiomBuilder.persist(storageConnection);
208: persistPendingReferences(entity, axiomBuilder.getSubjectIdentifier());
209: } catch (IllegalArgumentException e) {
210: throw new EntityDeconstructionException("Unable to deconstruct entity " + entity, e);
211: }
212: }
213:
214: @Override
215: public URI generateIdentifier(EntityType<?> et) {
216: try {
217: return storageConnection.generateIdentifier(et.getIRI().toURI());
218: } catch (OntoDriverException e) {
219: throw new StorageAccessException(e);
220: }
221: }
222:
223: private <T> void persistPendingReferences(T instance, NamedResource identifier) {
224: try {
225: final Set<PendingAssertion> pas = pendingReferences.removeAndGetPendingAssertionsWith(instance);
226: for (PendingAssertion pa : pas) {
227: final AxiomValueDescriptor desc = new AxiomValueDescriptor(pa.getOwner());
228: desc.addAssertionValue(pa.getAssertion(), new Value<>(identifier));
229: desc.setAssertionContext(pa.getAssertion(), pa.getContext());
230: storageConnection.persist(desc);
231: }
232: final Set<PendingReferenceRegistry.PendingListReference> pLists =
233: pendingReferences.removeAndGetPendingListReferencesWith(instance);
234: final EntityType<?> et = getEntityType(instance.getClass());
235: for (PendingReferenceRegistry.PendingListReference list : pLists) {
236: final ListValueDescriptor desc = list.getDescriptor();
237: ListPropertyStrategy.addIndividualsToDescriptor(desc, list.getValues(), et);
238: if (desc instanceof SimpleListValueDescriptor) {
239: // This can be a persist or an update, calling update works for both
240: storageConnection.lists().updateSimpleList((SimpleListValueDescriptor) desc);
241: } else {
242: storageConnection.lists().updateReferencedList((ReferencedListValueDescriptor) desc);
243: }
244: }
245: } catch (OntoDriverException e) {
246: throw new StorageAccessException(e);
247: }
248: }
249:
250: @Override
251: public <T> T getEntityFromCacheOrOntology(Class<T> cls, URI identifier, Descriptor descriptor) {
252: final T orig = uow.getManagedOriginal(cls, identifier, descriptor);
253: if (orig != null) {
254: return orig;
255: }
256: if (getCache().contains(cls, identifier, descriptor)) {
257: return defaultInstanceLoader.loadCached(getEntityType(cls), identifier, descriptor);
258: } else if (instanceRegistry.containsKey(identifier)) {
259: final Object existing = instanceRegistry.get(identifier);
260: if (!cls.isAssignableFrom(existing.getClass())) {
261: throw individualAlreadyManaged(identifier);
262: }
263: // This prevents endless cycles in bidirectional relationships
264: return cls.cast(existing);
265: } else {
266: return loadEntityInternal(new LoadingParameters<>(cls, identifier, descriptor));
267: }
268: }
269:
270: @Override
271: public <T> T getOriginalInstance(T clone) {
272: assert clone != null;
273: return (T) uow.getOriginal(clone);
274: }
275:
276: boolean isManaged(Object instance) {
277: return uow.isObjectManaged(instance);
278: }
279:
280: <T> void registerInstance(URI identifier, T instance) {
281: instanceRegistry.put(identifier, instance);
282: }
283:
284: @Override
285: public void checkForUnpersistedChanges() {
286: if (pendingReferences.hasPendingResources()) {
287: throw new UnpersistedChangeException(
288: "The following instances were neither persisted nor marked as cascade for persist: "
289: + pendingReferences.getPendingResources());
290: }
291: }
292:
293: void registerPendingAssertion(NamedResource owner, Assertion assertion, Object object, URI context) {
294: pendingReferences.addPendingAssertion(owner, assertion, object, context);
295: }
296:
297: void registerPendingListReference(Object item, ListValueDescriptor listDescriptor, List<?> values) {
298: pendingReferences.addPendingListReference(item, listDescriptor, values);
299: }
300:
301: @Override
302: public <T> void removeEntity(URI identifier, Class<T> cls, Descriptor descriptor) {
303: final EntityType<T> et = getEntityType(cls);
304: final AxiomDescriptor axiomDescriptor = descriptorFactory.createForEntityLoading(
305: new LoadingParameters<>(cls, identifier, descriptor, true), et);
306: try {
307: storageConnection.remove(axiomDescriptor);
308: pendingReferences.removePendingReferences(axiomDescriptor.getSubject());
309: } catch (OntoDriverException e) {
310: throw new StorageAccessException("Exception caught when removing entity.", e);
311: }
312: }
313:
314: @Override
315: public <T> void updateFieldValue(T entity, FieldSpecification<? super T, ?> fieldSpec,
316: Descriptor entityDescriptor) {
317: @SuppressWarnings("unchecked") final EntityType<T> et = (EntityType<T>) getEntityType(entity.getClass());
318: final URI pkUri = EntityPropertiesUtils.getIdentifier(entity, et);
319:
320: entityBreaker.setReferenceSavingResolver(new ReferenceSavingResolver(this));
321: // It is OK to do it like this, because if necessary, the mapping will re-register a pending assertion
322: removePendingAssertions(fieldSpec, pkUri);
323: final AxiomValueGatherer axiomBuilder =
324: entityBreaker.mapFieldToAxioms(pkUri, entity, fieldSpec, et, entityDescriptor);
325: axiomBuilder.update(storageConnection);
326: }
327:
328: private <T> void removePendingAssertions(FieldSpecification<? super T, ?> fs, URI identifier) {
329: if (fs instanceof Attribute<?, ?> att) {
330: // We care only about object property assertions, others are never pending
331: final Assertion assertion = Assertion.createObjectPropertyAssertion(att.getIRI().toURI(), att.isInferred());
332: pendingReferences.removePendingReferences(NamedResource.create(identifier), assertion);
333: }
334: }
335:
336: @Override
337: public Collection<Axiom<NamedResource>> loadSimpleList(SimpleListDescriptor listDescriptor) {
338: try {
339: return storageConnection.lists().loadSimpleList(listDescriptor);
340: } catch (OntoDriverException e) {
341: throw new StorageAccessException(e);
342: }
343: }
344:
345: @Override
346: public Collection<Axiom<?>> loadReferencedList(ReferencedListDescriptor listDescriptor) {
347: try {
348: return storageConnection.lists().loadReferencedList(listDescriptor);
349: } catch (OntoDriverException e) {
350: throw new StorageAccessException(e);
351: }
352: }
353:
354: @Override
355: public Configuration getConfiguration() {
356: return uow.getConfiguration();
357: }
358:
359: @Override
360: public <T> Set<Axiom<?>> getAttributeAxioms(T entity, FieldSpecification<? super T, ?> fieldSpec,
361: Descriptor entityDescriptor) {
362: final EntityType<T> et = (EntityType<T>) getEntityType(entity.getClass());
363: return FieldStrategy.createFieldStrategy(et, fieldSpec, entityDescriptor, this).buildAxiomsFromInstance(entity);
364: }
365:
366: @Override
367: public boolean isInferred(Axiom<?> axiom, URI context) {
368: try {
369: return storageConnection.isInferred(axiom, context != null ? Collections.singleton(context) :
370: Collections.emptySet());
371: } catch (OntoDriverException e) {
372: throw new StorageAccessException(e);
373: }
374: }
375:
376: @Override
377: public <T> boolean isInferred(T entity, FieldSpecification<? super T, ?> fieldSpec, Object value,
378: Descriptor entityDescriptor) {
379: final EntityType<T> et = (EntityType<T>) getEntityType(entity.getClass());
380: final FieldStrategy<?, ?> fs = FieldStrategy.createFieldStrategy(et, fieldSpec, entityDescriptor, this);
381: final Collection<Value<?>> values = fs.toAxiomValue(value);
382: final Set<URI> contexts = entityDescriptor.getAttributeContexts(fieldSpec);
383: final NamedResource subject = NamedResource.create(EntityPropertiesUtils.getIdentifier(entity, et));
384: return values.stream().map(v -> {
385: try {
386: return storageConnection.isInferred(new AxiomImpl<>(subject, fs.createAssertion(), v), contexts);
387: } catch (OntoDriverException e) {
388: throw new StorageAccessException(e);
389: }
390: }).reduce(false, Boolean::logicalOr);
391: }
392:
393: public UnitOfWork getUow() {
394: return uow;
395: }
396: }